5.1.3 koa 中间件实现原理
洋葱模型(中间件模型), koa 框架的中间件模型非常好用并且简洁, 但是也有自身的缺陷, 一旦中间件数组过于庞大, 性能会有所下降,
每个中间件默认接受两个参数,第一个参数是 Context 对象,第二个参数是 next 函数。只要调用 next 函数,就可以把执行权转交给下一个中间件。 如果中间件内部没有调用 next 函数,那么执行权就不会传递下去。 多个中间件会形成一个栈结构(middle stack),以“先进后出”(first-in-last-out)的顺序执行。整个过程就像,先是入栈,然后出栈的操作。
- 参考: 理解 Koa 框架中间件原理
Koa中间件的设计
Koa中间件与connect中间件的设计有很大的差异:
- Koa中间件的执行并不需要匹配路由,所以注册的中间件每一次请求都会执行。(当然还是需要手动调用next);
- Koa中通过继承event,暴露error事件让开发者自定义异常处理;
- Koa中res.end由中间件执行完成之后自动调用,这样避免在connect忘记调用res.end导致用户得不到任何反馈。
- Koa中采用了async/await语法让开发者利用同步的方式编写异步代码。
当然,Koa中也是采用use方法注册中间件,相比较connect省去路由匹配的处理,就显得很简洁:
use(fn) {
this.middleware.push(fn);
return this;
}
1
2
3
4
2
3
4
并且use支持链式调用。
Koa中间件的执行流程主要通过koa-compose中的compose函数完成:
function compose (middleware) {
if (!Array.isArray(middleware)) throw new TypeError('Middleware stack must be an array!')
for (const fn of middleware) {
if (typeof fn !== 'function') throw new TypeError('Middleware must be composed of functions!')
}
/**
* @param {Object} context
* @return {Promise}
* @api public
*/
return function (context, next) {
let index = -1
return dispatch(0)
function dispatch (i) {
if (i <= index) return Promise.reject(new Error('next() called multiple times'))
index = i
let fn = middleware[i]
if (i === middleware.length) fn = next
if (!fn) return Promise.resolve()
try {
// 递归调用下一个中间件
return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));
} catch (err) {
return Promise.reject(err)
}
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
看到这里本质上connect与koa实现中间件的思想都是递归,不难看出koa相比较connect实现得更加简洁,主要原因在于:
- connect中提供路由匹配的功能,而Koa中则是相当于connect中默认的'/'路径。
- connect在捕获中间件的异常时,通过next携带error一个个中间件验证,直到错误处理中间件,
而Koa中则是用Promise包装中间件,一旦中间件发生异常,那么会直接触发reject状态,直接在Promise的catch中处理就行。